'use strict'

process.env.NODE_ENV = 'development'

const chalk = require('chalk')
const electron = require('electron')
const path = require('path')
const { say } = require('cfonts')
const execa = require('execa')
const webpack = require('webpack')
const WebpackDevServer = require('webpack-dev-server')

const mainConfig = require('./webpack/main.config')
const rendererConfig = require('./webpack/renderer.config')

/**
 * 子进程：electron
 */
let child = null

function logStats(proc, data) {
    let log = ''

    log += chalk.yellow.bold(`┏ ${proc} Process ${new Array((19 - proc.length) + 1).join('-')}`)
    log += '\n\n'

    if (typeof data === 'object') {
        data.toString({
            colors: true,
            modules: false,
            children: false
        }).split(/\r?\n/).forEach(line => {
            log += '  ' + line + '\n'
        })
    } else {
        log += `  ${data}\n`
    }

    log += '\n' + chalk.yellow.bold(`┗ ${new Array(28 + 1).join('-')}`) + '\n'

    console.log(log)
}

function startRenderer() {
    return new Promise((resolve, reject) => {
        rendererConfig.mode = 'development'
        const compiler = webpack(rendererConfig)

        compiler.hooks.done.tap('done', stats => {
            logStats('Renderer', stats)
        })

        const server = new WebpackDevServer(
            compiler,
            {
                clientLogLevel: 'warning',
                contentBase: path.join(__dirname, '../'),
                quiet: true,
                hot: true,
                before(app, server) {
                    server.middleware.waitUntilValid(() => {
                        resolve()
                    })
                }
            }
        )
        server.listen(9080)
    })
}

function startMain() {
    return new Promise(async (resolve) => {
        mainConfig.mode = 'development'
        const compiler = webpack(mainConfig)

        compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => {
            logStats('Main', chalk.white.bold('compiling...'))
            done()
        })

        compiler.watch({}, async (err, stats) => {
            if (err) {
                console.log(err)
                return
            }

            logStats('Main', stats)

            await startElectron()
            resolve()
        })
    })
}

async function startElectron() {
    if (child) {
        child.removeListener('exit', onChildExit)
    }

    const waitTimeout = setTimeout(() => {
        electronLog('等待 Electron 退出...', 'yellow')
    }, 500)
    await killElectron()
    clearTimeout(waitTimeout)

    const args = [
        '--inspect=5858',
        path.join(__dirname, '../dist/electron/main.js')
    ]

    const stdioConfig = ['pipe', 'pipe', 'pipe']

    // Use an IPC on Windows for graceful exit
    if (process.platform === 'win32') {
        stdioConfig.push('ipc')
    }

    child = execa(electron, args,
        {
            cwd: path.resolve(__dirname, '../'),
            env: {
                ...process.env,
                // Disable electron security warnings
                ELECTRON_DISABLE_SECURITY_WARNINGS: true
            },
            stdio: stdioConfig,
            windowsHide: false
        }
    )

    child.stdout.on('data', (data) => {
        electronLog(data, 'blue')
    })
    child.stderr.on('data', (data) => {
        electronLog(data, 'red')
    })
    child.on('exit', onChildExit)
}
function onChildExit() {
    process.exit(0)
}

/**
 * 结束 electron 进程
 */
function killElectron() {
    return new Promise((resolve) => {
        if (!child || child.killed) {
            return resolve()
        }

        const currentChild = child
        currentChild.on('exit', () => {
            resolve()
        })

        // 尝试优雅地结束进程
        if (process.platform === 'win32') {
            currentChild.send('graceful-exit')
        } else {
            currentChild.kill('SIGTERM')
        }

        // 无法优雅，则 2 秒后强制结束
        setTimeout(() => {
            if (!currentChild.killed) {
                electronLog(`强制结束 Electron (进程 #${currentChild.pid})`, 'red')
                currentChild.kill('SIGKILL')
            }
        }, 2000)
    })
}


function electronLog(data, color) {
    let log = ''
    data = data.toString().split(/\r?\n/)
    data.forEach(line => {
        log += `  ${line}\n`
    })
    if (/[0-9A-z]+/.test(log)) {
        console.log(
            chalk[color].bold('┏ Electron -------------------') +
            '\n\n' +
            log +
            chalk[color].bold('┗ ----------------------------') +
            '\n'
        )
    }
}

function greeting() {
    const cols = process.stdout.columns
    let text = ''

    if (cols > 104) text = 'electron-vue'
    else if (cols > 76) text = 'electron-|vue'
    else text = false

    if (text) {
        say(text, {
            colors: ['yellow'],
            font: 'simple3d',
            space: false
        })
    } else {
        console.log(chalk.yellow.bold('\n  electron-vue'))
    }
    
    console.log('\n')
}

async function init() {
    greeting()

    const signalHandler = () => {
        if (!child) {
            process.exit(0)
        }

        killElectron()
    }

    process.on('SIGINT', signalHandler)
    process.on('SIGTERM', signalHandler)

    // Handle Ctrl+C on Windows
    if (process.platform === 'win32') {
        require('readline')
            .createInterface({
                input: process.stdin,
                output: process.stdout
            })
            .on('SIGINT', () => {
                process.emit('SIGINT')
            })
    }

    try {
        await startRenderer()
        startMain()
    } catch (err) {
        console.error(err)
    }
}

init()
